﻿// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.

using System.Drawing;
using System.Windows.Forms;
using System.Windows.Forms.Design;
using System.Windows.Forms.Design.Behavior;

namespace System.ComponentModel.Design;

/// <summary>
///  The DesignerActionUI is the designer/UI-specific implementation of the DesignerActions feature.
///  This class instantiates the DesignerActionService and hooks to its DesignerActionsChanged event.
///  Responding to this single event will enable the DesignerActionUI to perform all necessary UI-related operations.
///  Note that the DesignerActionUI uses the BehaviorService to manage all UI interaction.
///  For every component containing a DesignerAction (determined by the DesignerActionsChanged event)
///  there will be an associated DesignerActionGlyph and DesignerActionBehavior.
///  Finally, the DesignerActionUI is also responsible for showing and managing the Action's context menus.
///  Note that every DesignerAction context menu has an item that will bring up the DesignerActions
///  option pane in the options dialog.
/// </summary>
internal partial class DesignerActionUI : IDisposable
{
    private Adorner _designerActionAdorner; // used to add designeraction-related glyphs
    private IServiceProvider _serviceProvider; // standard service provider
    private ISelectionService _selectionService; // used to determine if comps have selection or not
    private DesignerActionService _designerActionService; // this is how all designeractions will be managed
    private DesignerActionUIService _designerActionUIService; // this is how all designeractions UI elements will be managed
    private BehaviorService _behaviorService; // this is how all of our UI is implemented (glyphs, behaviors, etc...)
    private DesignerActionKeyboardBehavior? _designerActionKeyboardBehavior;   // out keyboard behavior
    private readonly Dictionary<object, DesignerActionGlyph> _componentToGlyph; // used for quick reference between components and our glyphs
    private Control _marshalingControl; // used to invoke events on our main gui thread
    private IComponent? _lastPanelComponent;

    private readonly IWin32Window? _mainParentWindow;
    internal DesignerActionToolStripDropDown? _designerActionHost;

    private readonly MenuCommand? _cmdShowDesignerActions; // used to respond to the Alt+Shift+F10 command
    private bool _inTransaction;
    private IComponent? _relatedComponentTransaction;
    private DesignerActionGlyph? _relatedGlyphTransaction;
    private readonly bool _disposeActionService;
    private readonly bool _disposeActionUIService;
    private bool _cancelClose;

    private delegate void ActionChangedEventHandler(object sender, DesignerActionListsChangedEventArgs e);

    /// <summary>
    ///  Constructor that takes a service provider. This is needed to establish references to the BehaviorService
    ///  and SelectionService, as well as spin-up the DesignerActionService.
    /// </summary>
    public DesignerActionUI(IServiceProvider serviceProvider, Adorner containerAdorner)
    {
        _serviceProvider = serviceProvider;
        _designerActionAdorner = containerAdorner;
        IMenuCommandService? menuCommandService = serviceProvider.GetService<IMenuCommandService>();
        if (!serviceProvider.TryGetService(out BehaviorService? behaviorService) ||
            !serviceProvider.TryGetService(out ISelectionService? selectionService))
        {
            Debug.Fail("Either BehaviorService or ISelectionService is null, cannot continue.");
            return;
        }

        _behaviorService = behaviorService;
        _selectionService = selectionService;

        // query for our DesignerActionService
        if (!serviceProvider.TryGetService(out DesignerActionService? designerActionService))
        {
            // start the service
            designerActionService = new DesignerActionService(serviceProvider);
            _disposeActionService = true;
        }

        if (!serviceProvider.TryGetService(out DesignerActionUIService? designerActionUIService))
        {
            designerActionUIService = new DesignerActionUIService(serviceProvider);
            _disposeActionUIService = true;
        }

        _designerActionService = designerActionService;
        _designerActionUIService = designerActionUIService;

        _designerActionUIService.DesignerActionUIStateChange += OnDesignerActionUIStateChange;
        _designerActionService.DesignerActionListsChanged += OnDesignerActionsChanged;
        _lastPanelComponent = null;

        if (serviceProvider.TryGetService(out IComponentChangeService? cs))
        {
            cs.ComponentChanged += OnComponentChanged;
        }

        if (menuCommandService is not null)
        {
            _cmdShowDesignerActions = new MenuCommand(OnKeyShowDesignerActions, MenuCommands.KeyInvokeSmartTag);
            menuCommandService.AddCommand(_cmdShowDesignerActions);
        }

        if (serviceProvider.TryGetService(out IUIService? uiService))
        {
            _mainParentWindow = uiService.GetDialogOwnerWindow();
        }

        _componentToGlyph = [];
        _marshalingControl = new Control();
        _marshalingControl.CreateControl();
    }

    /// <summary>
    ///  Disposes all UI-related objects and unhooks services.
    /// </summary>
    // Don't need to dispose of designerActionUIService.
    public void Dispose()
    {
        if (_marshalingControl is not null)
        {
            _marshalingControl.Dispose();
            _marshalingControl = null!;
        }

        if (_serviceProvider is not null)
        {
            if (_serviceProvider.TryGetService(out IComponentChangeService? cs))
            {
                cs.ComponentChanged -= OnComponentChanged;
            }

            if (_cmdShowDesignerActions is not null)
            {
                IMenuCommandService? mcs = _serviceProvider.GetService<IMenuCommandService>();
                mcs?.RemoveCommand(_cmdShowDesignerActions);
            }
        }

        _serviceProvider = null!;
        _behaviorService = null!;
        _selectionService = null!;

        if (_designerActionService is not null)
        {
            _designerActionService.DesignerActionListsChanged -= OnDesignerActionsChanged;
            if (_disposeActionService)
            {
                _designerActionService.Dispose();
            }
        }

        _designerActionService = null!;

        if (_designerActionUIService is not null)
        {
            _designerActionUIService.DesignerActionUIStateChange -= OnDesignerActionUIStateChange;
            if (_disposeActionUIService)
            {
                _designerActionUIService.Dispose();
            }
        }

        _designerActionUIService = null!;
        _designerActionAdorner = null!;
    }

    public DesignerActionGlyph? GetDesignerActionGlyph(IComponent comp)
    {
        return GetDesignerActionGlyph(comp, null);
    }

    internal DesignerActionGlyph? GetDesignerActionGlyph(IComponent comp, DesignerActionListCollection? dalColl)
    {
        // check this component origin, this class or is it readonly because inherited...
        InheritanceAttribute? attribute = (InheritanceAttribute?)TypeDescriptor.GetAttributes(comp)[typeof(InheritanceAttribute)];
        if (attribute == InheritanceAttribute.InheritedReadOnly)
        {
            // Only do it if we can change the control.
            return null;
        }

        // we didnt get on, fetch it
        dalColl ??= _designerActionService.GetComponentActions(comp);

        if (dalColl is not null && dalColl.Count > 0)
        {
            if (!_componentToGlyph.TryGetValue(comp, out DesignerActionGlyph? designerActionGlyph))
            {
                DesignerActionBehavior dab = new(_serviceProvider, comp, dalColl, this);

                // if comp is a component then try to find a TrayControl associated with it...
                // this should really be in ComponentTray but there is no behaviorService for the CT
                if (comp is not Control or ToolStripDropDown)
                {
                    // Here, we'll try to get the TrayControl associated with the comp and supply the glyph with an alternative bounds
                    ComponentTray? componentTray = _serviceProvider.GetService<ComponentTray>();
                    if (componentTray is not null)
                    {
                        ComponentTray.TrayControl trayControl = ComponentTray.GetTrayControlFromComponent(comp);
                        if (trayControl is not null)
                        {
                            Rectangle trayBounds = trayControl.Bounds;
                            designerActionGlyph = new DesignerActionGlyph(dab, trayBounds, componentTray);
                        }
                    }
                }

                // either comp is a control or we failed to find a traycontrol
                // (which could be the case for toolstripitem components) - in this case just create a standard glyph.
                // if the related comp is a control, then this shortcut will be off its bounds
                designerActionGlyph ??= new DesignerActionGlyph(dab, _designerActionAdorner);

                // store off this relationship
                _componentToGlyph[comp] = designerActionGlyph;
            }
            else
            {
                if (designerActionGlyph.Behavior is DesignerActionBehavior behavior)
                {
                    behavior.ActionLists = dalColl;
                }

                designerActionGlyph.Invalidate(); // need to invalidate here too, someone could have called refresh too soon, causing the glyph to get created in the wrong place
            }

            return designerActionGlyph;
        }
        else
        {
            // the list is now empty... remove the panel and glyph for this control
            RemoveActionGlyph(comp);
            return null;
        }
    }

    /// <summary>
    ///  We monitor this event so we can update smart tag locations when controls move.
    /// </summary>
    private void OnComponentChanged(object? source, ComponentChangedEventArgs ce)
    {
        // validate event args
        if (ce.Component is null || ce.Member is null || !IsDesignerActionPanelVisible)
        {
            return;
        }

        // If the smart tag is showing, we only move the smart tag if the changing component is the component
        // for the currently showing smart tag.
        if (_lastPanelComponent is not null && !_lastPanelComponent.Equals(ce.Component))
        {
            return;
        }

        // if something changed on a component we have actions associated with then invalidate all (repaint & reposition)
        if (_componentToGlyph.TryGetValue(ce.Component, out DesignerActionGlyph? glyph))
        {
            glyph.Invalidate();

            if (ce.Member.Name.Equals("Dock"))
            { // this is the only case were we don't require an explicit refresh
                RecreatePanel(ce.Component as IComponent); // because 99% of the time the action is name "dock in parent container" and get replaced by "undock"
            }

            if (ce.Member.Name.Equals("Location") ||
                 ce.Member.Name.Equals("Width") ||
                 ce.Member.Name.Equals("Height"))
            {
                // we don't need to regen, we just need to update location calculate the position of the form hosting the panel
                UpdateDAPLocation(ce.Component as IComponent, glyph);
            }
        }
    }

    private void RecreatePanel(IComponent? comp)
    {
        if (_inTransaction || comp is null || comp != _selectionService.PrimarySelection)
        {
            // we only ever need to do that when the comp is the primary selection
            return;
        }

        // we check whether or not we're in a transaction, if we are, we only the refresh at the end of the transaction to avoid flicker.
        IDesignerHost? host = _serviceProvider.GetService<IDesignerHost>();
        if (host is { InTransaction: true } and not IDesignerHostTransactionState { IsClosingTransaction: true })
        {
            host.TransactionClosed += DesignerTransactionClosed;
            _inTransaction = true;
            _relatedComponentTransaction = comp;
            return;
        }

        RecreateInternal(comp);
    }

    private void DesignerTransactionClosed(object? sender, DesignerTransactionCloseEventArgs e)
    {
        if (e.LastTransaction && _relatedComponentTransaction is not null)
        {
            // We can get multiple even with e.LastTransaction set to true, even though we unhook here. This is because
            // the list on which we enumerate (the event handler list) is copied before it's enumerated on which means
            // that if the undo engine for example creates and commit a transaction during the OnCancel of another
            // completed transaction we will get this twice. So we have to check also for relatedComponentTransaction
            // is not null.

            _inTransaction = false;
            IDesignerHost host = _serviceProvider.GetRequiredService<IDesignerHost>();
            host.TransactionClosed -= DesignerTransactionClosed;
            RecreateInternal(_relatedComponentTransaction);
            _relatedComponentTransaction = null;
        }
    }

    private void RecreateInternal(IComponent comp)
    {
        DesignerActionGlyph? glyph = GetDesignerActionGlyph(comp);
        if (glyph is not null)
        {
            VerifyGlyphIsInAdorner(glyph);

            // This could happen when a verb change state or suddenly a control gets a new action in the panel and we
            // are the primary selection in that case there would not be a glyph active in the adorner to be shown
            // because we update that on selection change. We have to do that here too.

            RecreatePanel(glyph);
            UpdateDAPLocation(comp, glyph);
        }
    }

    private void RecreatePanel(Glyph glyphWithPanelToRegen)
    {
        // We don't want to do anything if the panel is not visible.
        if (!IsDesignerActionPanelVisible)
        {
            return;
        }

        // recreate a designeraction panel
        if (glyphWithPanelToRegen.Behavior is DesignerActionBehavior behaviorWithPanelToRegen)
        {
            Debug.Assert(behaviorWithPanelToRegen.RelatedComponent is not null, "could not find related component for this refresh");
            DesignerActionPanel? dap = _designerActionHost.CurrentPanel; // WE DO NOT RECREATE THE WHOLE THING / WE UPDATE THE TASKS - should flicker less
            dap?.UpdateTasks(behaviorWithPanelToRegen.ActionLists,
                [],
                string.Format(SR.DesignerActionPanel_DefaultPanelTitle, behaviorWithPanelToRegen.RelatedComponent.GetType().Name),
                subtitle: null);
            _designerActionHost.UpdateContainerSize();
        }
    }

    private void VerifyGlyphIsInAdorner(DesignerActionGlyph glyph)
    {
        if (glyph.IsInComponentTray)
        {
            ComponentTray? compTray = _serviceProvider.GetService<ComponentTray>();
            if (compTray?.SelectionGlyphs is not null && !compTray.SelectionGlyphs.Contains(glyph))
            {
                compTray.SelectionGlyphs.Insert(0, glyph);
            }
        }
        else
        {
            if (_designerActionAdorner?.Glyphs is { } glyphs && !glyphs.Contains(glyph))
            {
                _designerActionAdorner.Glyphs.Insert(0, glyph);
            }
        }

        glyph.InvalidateOwnerLocation();
    }

    /// <summary>
    ///  This event is fired by the DesignerActionService in response to a DesignerActionCollection changing.
    ///  The event args contains information about the related object, the type of change (added or removed)
    ///  and the remaining DesignerActionCollection for the object. Note that when new DesignerActions are added,
    ///  if the related control/ is not yet parented - we add these actions to a "delay" list and they are later
    ///  created when the control is finally parented.
    /// </summary>
    private void OnDesignerActionsChanged(object sender, DesignerActionListsChangedEventArgs e)
    {
        // We need to invoke this async because the designer action service will raise this event from the thread pool.
        if (_marshalingControl is not null && _marshalingControl.IsHandleCreated)
        {
            _marshalingControl.BeginInvoke(new ActionChangedEventHandler(OnInvokedDesignerActionChanged), [sender, e]);
        }
    }

    private void OnDesignerActionUIStateChange(object sender, DesignerActionUIStateChangeEventArgs e)
    {
        if (e.RelatedObject is IComponent component)
        {
            DesignerActionGlyph? relatedGlyph = GetDesignerActionGlyph(component);
            if (relatedGlyph is not null)
            {
                if (e.ChangeType == DesignerActionUIStateChangeType.Show)
                {
                    if (relatedGlyph.Behavior is DesignerActionBehavior behavior)
                    {
                        behavior.ShowUI(relatedGlyph);
                    }
                }
                else if (e.ChangeType == DesignerActionUIStateChangeType.Hide)
                {
                    if (relatedGlyph.Behavior is DesignerActionBehavior behavior)
                    {
                        behavior.HideUI();
                    }
                }
                else if (e.ChangeType == DesignerActionUIStateChangeType.Refresh)
                {
                    relatedGlyph.Invalidate();
                    RecreatePanel(component);
                }
            }
        }
        else if (e.ChangeType == DesignerActionUIStateChangeType.Hide)
        {
            HideDesignerActionPanel();
        }
        else
        {
            Debug.Fail("related object is not an IComponent, something is wrong here...");
        }
    }

    /// <summary>
    ///  This is the same as DesignerActionChanged, but it is invoked on our control's thread
    /// </summary>
    private void OnInvokedDesignerActionChanged(object sender, DesignerActionListsChangedEventArgs e)
    {
        DesignerActionGlyph? g = null;
        if (e.ChangeType == DesignerActionListsChangedType.ActionListsAdded)
        {
            if (e.RelatedObject is not IComponent relatedComponent)
            {
                Debug.Fail("How can we add a DesignerAction glyphs when it's related object is not  an IComponent?");
                return;
            }

            IComponent? primSel = _selectionService.PrimarySelection as IComponent;
            if (primSel == e.RelatedObject)
            {
                g = GetDesignerActionGlyph(relatedComponent, e.ActionLists);
                if (g is not null)
                {
                    VerifyGlyphIsInAdorner(g);
                }
                else
                {
                    RemoveActionGlyph(e.RelatedObject);
                }
            }
        }

        if (e.ChangeType == DesignerActionListsChangedType.ActionListsRemoved && e.ActionLists!.Count == 0)
        {
            // only remove our glyph if there are no more DesignerActions associated with it.
            RemoveActionGlyph(e.RelatedObject);
        }
        else if (g is not null)
        {
            // we need to recreate the panel here, since it's content has changed...
            RecreatePanel(e.RelatedObject as IComponent);
        }
    }

    /// <summary>
    ///  Called when our KeyShowDesignerActions menu command is fired
    ///  (a.k.a. Alt+Shift+F10) - we will find the primary selection,
    ///  see if it has designer actions, and if so - show the menu.
    /// </summary>
    private void OnKeyShowDesignerActions(object? sender, EventArgs e)
    {
        ShowDesignerActionPanelForPrimarySelection();
    }

    // we cannot attach several menu command to the same command id, we need a single entry point,
    // we put it in designershortcutui. but we need a way to call the show ui on the related behavior
    // hence this internal function to hack it together. we return false if we have nothing to display,
    // we hide it and return true if we're already displaying
    internal bool ShowDesignerActionPanelForPrimarySelection()
    {
        // can't do anything w/o selection service
        if (_selectionService is null)
        {
            return false;
        }

        object? primarySelection = _selectionService.PrimarySelection;
        // verify that we have obtained a valid component with designer actions
        if (primarySelection is null || !_componentToGlyph.TryGetValue(primarySelection, out DesignerActionGlyph? glyph))
        {
            return false;
        }

        if (glyph.Behavior is DesignerActionBehavior behavior)
        // show the menu
        {
            if (!IsDesignerActionPanelVisible)
            {
                behavior.ShowUI(glyph);
                return true;
            }
            else
            {
                behavior.HideUI();
                return false;
            }
        }

        return false;
    }

    /// <summary>
    ///  When all the DesignerActions have been removed for a particular object,
    ///  we remove any UI (glyphs) that we may have been managing.
    /// </summary>
    internal void RemoveActionGlyph(object? relatedObject)
    {
        if (relatedObject is null)
        {
            return;
        }

        if (IsDesignerActionPanelVisible && relatedObject == _lastPanelComponent)
        {
            HideDesignerActionPanel();
        }

        if (!_componentToGlyph.TryGetValue(relatedObject, out DesignerActionGlyph? glyph))
        {
            return;
        }

        // Check ComponentTray first
        _serviceProvider.GetService<ComponentTray>()?.SelectionGlyphs?.Remove(glyph);

        _designerActionAdorner.Glyphs.Remove(glyph);
        _componentToGlyph.Remove(relatedObject);

        // we only do this when we're in a transaction, see bug VSWHIDBEY 418709.
        // This is for compatibility reason - infragistic. if we're not in a transaction, too bad, we don't update the screen
        if (_serviceProvider.TryGetService(out IDesignerHost? host) && host.InTransaction)
        {
            host.TransactionClosed += InvalidateGlyphOnLastTransaction;
            _relatedGlyphTransaction = glyph;
        }
    }

    private void InvalidateGlyphOnLastTransaction(object? sender, DesignerTransactionCloseEventArgs e)
    {
        if (e.LastTransaction)
        {
            if (_serviceProvider.TryGetService(out IDesignerHost? host))
            {
                host.TransactionClosed -= InvalidateGlyphOnLastTransaction;
            }

            _relatedGlyphTransaction?.InvalidateOwnerLocation();

            _relatedGlyphTransaction = null;
        }
    }

    internal void HideDesignerActionPanel()
    {
        if (IsDesignerActionPanelVisible)
        {
            _designerActionHost.Close();
        }
    }

    [MemberNotNullWhen(true, nameof(_designerActionHost))]
    internal bool IsDesignerActionPanelVisible
    {
        get => (_designerActionHost is not null && _designerActionHost.Visible);
    }

    internal IComponent? LastPanelComponent
    {
        get => (IsDesignerActionPanelVisible ? _lastPanelComponent : null);
    }

    private void ToolStripDropDown_Closing(object? sender, ToolStripDropDownClosingEventArgs e)
    {
        if (_cancelClose || e.Cancel)
        {
            e.Cancel = true;
            return;
        }

        if (e.CloseReason == ToolStripDropDownCloseReason.ItemClicked)
        {
            e.Cancel = true;
        }

        if (e.CloseReason == ToolStripDropDownCloseReason.Keyboard)
        {
            e.Cancel = false;
        }

        if (!e.Cancel)
        {
            // We WILL disappear
            Debug.Assert(_lastPanelComponent is not null, "last panel component should not be null here... " +
                "(except if you're currently debugging VS where deactivation messages in the middle of the pump can mess up everything...)");

            if (_lastPanelComponent is null)
            {
                return;
            }

            // If we're actually closing get the coordinate of the last message, the one causing us to close, is it within
            // the glyph coordinate. if it is that mean that someone just clicked back from the panel, on VS, but ON THE GLYPH,
            // that means that he actually wants to close it. The activation change is going to do that for us but we should
            // NOT reopen right away because he clicked on the glyph. This code is here to prevent this.
            Point point = DesignerUtils.LastCursorPoint;
            if (_componentToGlyph.TryGetValue(_lastPanelComponent, out DesignerActionGlyph? currentGlyph))
            {
                Point glyphCoord = GetGlyphLocationScreenCoord(_lastPanelComponent, currentGlyph);
                if ((new Rectangle(glyphCoord, new Size(currentGlyph.Bounds.Width, currentGlyph.Bounds.Height))).Contains(point))
                {
                    DesignerActionBehavior behavior = (DesignerActionBehavior)currentGlyph.Behavior!;
                    behavior.IgnoreNextMouseUp = true;
                }

                currentGlyph.InvalidateOwnerLocation();
            }

            _lastPanelComponent = null;
            // panel is going away, pop the behavior that's on the stack...
            Debug.Assert(_designerActionKeyboardBehavior is not null, $"why is {nameof(_designerActionKeyboardBehavior)} null?");
            Behavior? popBehavior = _behaviorService.PopBehavior(_designerActionKeyboardBehavior);
            Debug.Assert(popBehavior is DesignerActionKeyboardBehavior, "behavior returned is of the wrong kind?");
        }
    }

    internal Point UpdateDAPLocation(IComponent? component, DesignerActionGlyph? glyph)
    {
        component ??= _lastPanelComponent;

        if (_designerActionHost is null)
        {
            return Point.Empty;
        }

        if (component is null || glyph is null)
        {
            return _designerActionHost.Location;
        }

        // check that the glyph is still visible in the adorner window
        if (_behaviorService is not null &&
            !_behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds))
        {
            HideDesignerActionPanel();
            return _designerActionHost.Location;
        }

        Point glyphLocationScreenCoord = GetGlyphLocationScreenCoord(component, glyph);
        Rectangle rectGlyph = new(glyphLocationScreenCoord, glyph.Bounds.Size);
        Point pt = DesignerActionPanel.ComputePreferredDesktopLocation(rectGlyph, _designerActionHost.Size, out DockStyle edgeToDock);
        glyph.DockEdge = edgeToDock;
        _designerActionHost.Location = pt;
        return pt;
    }

    private Point GetGlyphLocationScreenCoord(IComponent relatedComponent, Glyph glyph)
    {
        Point glyphLocationScreenCoord = new(0, 0);
        if (relatedComponent is Control and not ToolStripDropDown)
        {
            glyphLocationScreenCoord = _behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location);
        }

        // ISSUE: we can't have this special cased here - we should find a more generic approach to solving this problem
        else if (relatedComponent is ToolStripItem item)
        {
            if (item.Owner is not null)
            {
                glyphLocationScreenCoord = _behaviorService.AdornerWindowPointToScreen(glyph.Bounds.Location);
            }
        }
        else
        {
            if (_serviceProvider.GetService(typeof(ComponentTray)) is ComponentTray compTray)
            {
                glyphLocationScreenCoord = compTray.PointToScreen(glyph.Bounds.Location);
            }
        }

        return glyphLocationScreenCoord;
    }

    /// <summary>
    ///  This shows the actual chrome panel that is created by the DesignerActionBehavior object.
    /// </summary>
    internal void ShowDesignerActionPanel(IComponent relatedComponent, DesignerActionPanel panel, DesignerActionGlyph glyph)
    {
        if (_designerActionHost is null)
        {
            _designerActionHost = new DesignerActionToolStripDropDown(this, _mainParentWindow)
            {
                AutoSize = false,
                Padding = Padding.Empty,
                Renderer = new NoBorderRenderer(),
                Text = "DesignerActionTopLevelForm"
            };
            _designerActionHost.Closing += ToolStripDropDown_Closing;
        }

        // set the accessible name of the panel to the same title as the panel header. do that every time
        _designerActionHost.AccessibleName = string.Format(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name);
        panel.AccessibleName = string.Format(SR.DesignerActionPanel_DefaultPanelTitle, relatedComponent.GetType().Name);

        _designerActionHost.SetDesignerActionPanel(panel, glyph);
        Point location = UpdateDAPLocation(relatedComponent, glyph);

        // check that the panel will have at least it's parent glyph visible on the adorner window
        if (_behaviorService is not null &&
            _behaviorService.AdornerWindowControl.DisplayRectangle.IntersectsWith(glyph.Bounds))
        {
            if (_mainParentWindow is not null && _mainParentWindow.Handle != 0)
            {
                PInvokeCore.SetWindowLong(
                    _designerActionHost,
                    WINDOW_LONG_PTR_INDEX.GWL_HWNDPARENT,
                    new HandleRef<HWND>(_mainParentWindow, (HWND)_mainParentWindow.Handle));
            }

            _cancelClose = true;
            _designerActionHost.Show(location);
            _designerActionHost.Focus();

            // When a control is drag and dropped and autoshow is set to true the vs designer is going to get activated
            // as soon as the control is dropped we don't want to close the panel then, so we post a message (using the
            // trick to call begin invoke) and once everything is settled re-activate the autoclose logic.
            _designerActionHost.BeginInvoke(OnShowComplete);

            // invalidate the glyph to have it point the other way
            glyph.InvalidateOwnerLocation();
            _lastPanelComponent = relatedComponent;
            // push new behavior for keyboard handling on the behavior stack
            _designerActionKeyboardBehavior = new DesignerActionKeyboardBehavior(_designerActionHost.CurrentPanel, _serviceProvider, _behaviorService);
            _behaviorService.PushBehavior(_designerActionKeyboardBehavior);
        }
    }

    private void OnShowComplete()
    {
        _cancelClose = false;

        // Force the panel to be the active window - for some reason someone else could have forced VS to become active
        // for real while we were ignoring close. This might be bad cause we'd be in a bad state.
        if (_designerActionHost is not null && _designerActionHost.Handle != 0 && _designerActionHost.Visible)
        {
            PInvoke.SetActiveWindow(_designerActionHost);
            _designerActionHost.CheckFocusIsRight();
        }
    }
}
